Skip to content

perf(agc): pre-sync the DCC key writers of a dispatch's images before the GPU mutex - #6631

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
AlxcBTW:perf/dcc-key-presync
Oct 17, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
AlxcBTW:perf/dcc-key-presync

Conversation

@AlxcBTW

@AlxcBTW AlxcBTW commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Companion: #6632 (independent of this one; the two together give more than the sum, see Tested).

A compute dispatch builds its resources in two stages: stage A (binding plan, buffers, descriptor set) runs without the GPU mutex, stage B (the image lookups) under it. Between them VulkanDevice::presync waits, still without the mutex, for recorded GPU work that writes a surface stage B would read on the CPU, so that the locked lookup finds nothing pending and the other workers do not queue behind the wait. That list held only the texel surfaces. The DCC keys of an image are read on the CPU as well (TextureClearKeys, the clear-code decode of a sampled texture or a storage image), and when recorded work writes them with values the driver can't derive from the pending stores, WaitForKeyWriters synchronizes with the GPU under the mutex.

In Minecraft Dungeons II (PPSA16064) on its title screen (3840x2160, about 8 FPS) the pre-sync fired once in 296,000 dispatch checks while WaitForKeyWriters synchronized 400 times per 10 s, 1.17 s of waiting under the mutex per 10 s, about 90 % of it on the compute queue worker. The queue 0 worker and the draw committer waited 2.6 s per 10 s behind exactly the compute worker's holds.

  • Graphics::PresyncSurface (address, bytes, dccKeys) replaces the (address, bytes) pair that ShaderResources::PresyncSurfaces, Recipe::presyncSurfaces and VulkanDevice::presync passed around.
  • PresyncSurfaces adds the key range (one key byte per 256 surface bytes, DccKeyBytes) of every image binding with a DCC address, for the built object, the cached object and the address-based decode alike.
  • presync waits for a key range only while recorded work writes it (the lock-free pending-write snapshot), no key-store memo entry already gives its value (Graphics::DccKeyWritersPending, the same memo PendingStoreKeys consults) and, under the mutex it takes for a moment anyway, the pending stores do not give the keys either (Graphics::DccKeyWriterNeedsSync: a pending write overlaps and PendingStoreKeys has no value, the test WaitForKeyWriters makes before it synchronizes). Which keys a dispatch reads, and when, is unchanged: the locked lookup runs as before and finds nothing pending. A writer recorded after the check still takes the locked wait.
  • The [presync] line (APS5_PROFILE_DRAW) counts key ranges checked, pre-syncs that only the keys required, and the outcomes of WaitForKeyWriters (no pending writer, keys known, fills leaving uncompressed, synchronized). VulkanDevice::PresyncCounts() exposes the first two for the test.
  • APS5_NO_DCC_PRESYNC=1 leaves the key ranges out (APS5_NO_PRESYNC=1 already disables the whole pre-sync). docs/dev/ARCHITECTURE.md: one paragraph on stage A, the pre-sync and the key ranges.

Tested

Windows 11, MinGW-w64 GCC 15.2, NVIDIA GeForce RTX 5070 Laptop GPU (driver 617.42) for the title rows; lavapipe on Linux CI for the tests. The title rows ran on this change on top of 44df21632; the branch is squashed onto 5d9dcc42d (the 59 commits in between merge without a conflict and the changed lines are the same).

  • Test agc_driver_dcc_key_presync (new): a kernel that copies data over the DCC keys of a sampled texture is recorded. Control: a dispatch built under the mutex waits for it in WaitForKeyWriters (synced rises). With PrepareDispatch: stage A lists the key range and waits once before the mutex (PresyncCounts: keyChecks, keyWaits, presyncs), the dispatch then makes no locked synchronization, and both samples read the same keys (the texture reads as the 0000 clear the kernel stored). A second PrepareDispatch with nothing pending waits for nothing, and one behind a recorded constant fill of the keys, whose value the pending stores give, waits for nothing and the dispatch makes no locked synchronization either. The test skips with the reasons tools/check_skips.py expects when the device has no timeline semaphores or no host import. Fork CI (Linux, lavapipe, ubuntu-latest; this squashed head on origin/main 5d9dcc42d): https://github.com/AlxcBTW/AnyPS5/actions/runs/38817872674, 459 of 459 tests of ^(agc_driver_|agc_recorder|agc_dcc|agc_display_buffer_dcc|agc_pending|agc_key) pass, agc_driver_dcc_key_presync in 0.15 s.
  • Build: Windows libs and relinker compile on the fork CI (MinGW-w64 GCC): https://github.com/AlxcBTW/AnyPS5/actions/runs/38817909024 (this squashed head). tools/check_conventions.py --base origin/main passes.
  • Title rows (Minecraft Dungeons II title screen, 3840x2160, 150 s rows, the first three 10 s windows of each row dropped, APS5_TEXTURE_CACHE_MIB=3072, APS5_PROFILE_DRAW=1, APS5_PROFILE_GPU=1, APS5_NO_PAD_INPUT=1; the main arm is the binary with both switches off, which an earlier session measured against a build of origin/main: 7.80 against 7.93 FPS):
arm (one binary, both branches merged; the arms are switches) rows / windows FPS GPU busy per frame GPU idle per frame
main: APS5_NO_DCC_PRESYNC=1 APS5_HANDOFF_SPIN_US=0 2 / 16 7.35 80.5 ms 54.7 ms
this PR alone: APS5_HANDOFF_SPIN_US=0 2 / 16 7.64 (+4.0 %) 80.0 ms 49.8 ms
companion PR alone: APS5_NO_DCC_PRESYNC=1 2 / 16 7.57 (+3.0 %) 81.2 ms 46.3 ms
both 1 / 8 8.55 (+16.3 %) 79.6 ms 36.3 ms

Rows run in the order both, this PR, companion, main, main, companion, this PR, both. The warm-up row and the first both row, the first two after the relink, found no title windows (probably cold caches), so both has one row of eight windows. Rows of identical code differ by up to 0.8 FPS (the two main rows read 7.08 and 7.62, the two rows of the companion alone 7.16 and 7.97), about 10 %, so a single row cannot resolve an effect of that size; the pooled arms and the repetition in other sessions can. The same scene on the same machine in two earlier sessions: origin/main against the first commit of this PR with the companion, alternating rows, 7.93 against 8.76 FPS (+10.5 %); an earlier lab build, levers off against both levers on, 7.94 against 8.76 (+10.2 %), with this PR's lever alone at +2.6 % and the companion's alone at +2.9 %. The baseline therefore lands between 7.35 and 7.94 FPS from session to session, and the changed build between 8.55 and 8.76 FPS: +10 % to +16 %, +12 % on average of the three sessions. Alone, this PR gives +2.6 % and +4.0 %, the companion +2.9 % and +3.0 %; together they give more than the sum, and the GPU busy time stays at about 80 ms while its idle time per frame falls from 54.7 to 36.3 ms.

  • Performance report protocol (the scene ran once and the title was restarted, so the caches are warm; only APS5_TRACE_SECONDS=60:10 is set besides APS5_TEXTURE_CACHE_MIB=3072, APS5_NO_PAD_INPUT=1 and the trace directory; same machine, scene and session, one capture per build, no event dropped): origin/main, 81 presents in 10.0 s = 8.09 FPS, frame time p50 120.7 / p90 138.8 / p99 152.7 ms, GPU busy 79.2 ms; both PRs, 80 presents in 10.0 s = 7.97 FPS, p50 120.1 / p90 153.0 / p99 176.4 ms, GPU busy 78.5 ms. These two captures do not show the difference: a capture is 80 frames of one row, and rows of identical code differ by more than the effect (see above). The captures of the earlier session (20 s, APS5_TRACE_SECONDS=60:20, the queue 0 worker's event buffer full after 12 s, so they are flagged trace_dropped) read 8.27 FPS (p99 151.9 ms) for origin/main and 8.99 FPS (p99 130.6 ms) for the first commit of this PR with the companion (+8.7 %). The summary lines of the four captures follow as a comment; the traces are kept and available on request.
  • Not checked: other titles, other GPUs and drivers, macOS. The pre-sync still waits where only PendingFillsLeaveUncompressed would have answered in the locked lookup (it needs the texture's key encoding, which the surface list does not carry; the counter of those answers was 0 in every row), and it skips a range whose key-store memo entry is older than a later unknown writer (the locked lookup then waits as before).

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: NVIDIA GeForce RTX 5070 Laptop GPU, driver 617.42, Windows 11 (title rows); lavapipe (Mesa) on ubuntu-latest (tests)
  • AI-assisted: yes

@AlxcBTW

AlxcBTW commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Performance report captures for this PR and #6632 (Minecraft Dungeons II title screen, 3840x2160, RTX 5070 Laptop GPU, driver 617.42, Windows 11, warm caches; only APS5_TEXTURE_CACHE_MIB=3072, APS5_NO_PAD_INPUT=1 and the trace switches set). One capture is 80 to 180 frames of one row, and rows of identical code differ by up to 10 %, so the pooled 150 s rows in the description are the evidence; these are the protocol's captures.

capture what FPS
cl-main2 origin/main, 00:24, 10 s window 8.09 FPS
cl-final both PRs merged, final heads, 00:10, 10 s window 7.97 FPS
cl-main origin/main, 20 s window, queue 0 event buffer full after 12.1 s 8.27 FPS
cl-both first commit of the DCC pre-sync with the spin, 20 s window, buffer full after 13.2 s 8.99 FPS

Summary lines (aps5-summary.jsonl, verbatim; paths redacted by the tool):
cl-main2:

{"commit":"3a84d7c90ec6","dirty":false,"env":{"APS5_BATCH_WRITEBACK_WINDOWS":"0","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_TEXTURE_CACHE_MIB":"3072","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 5070 Laptop GPU","driver":"NVIDIA 617.42","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 60:10","first_present":1641,"ms":10009.716},"presents":81,"frame_ms":{"p50":120.725,"p90":138.825,"p99":152.702,"max":152.702},"gpu_busy_ms_per_present":79.229,"worker_busy_pct":{"queue worker 0x0":99.328,"queue worker 0x20":99.104},"top_phases_ms_per_frame":{"Driver.Packet":245.067,"Driver.Packet.dispatch":80.018,"Driver.Dispatch":78.807,"Driver.Packet.memory_wait":56.850,"Vulkan.Dispatch":43.633,"Driver.Packet.draw":42.647,"Driver.Packet.device_lock":42.642,"Driver.Draw":42.514},"trace_dropped":false,"trace":"aps5-trace-3a84d7c90ec6-31640.json.gz"}

cl-final:

{"commit":"184c4f6ff78a","dirty":false,"env":{"APS5_BATCH_WRITEBACK_WINDOWS":"0","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_TEXTURE_CACHE_MIB":"3072","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:10"},"gpu":"NVIDIA GeForce RTX 5070 Laptop GPU","driver":"NVIDIA 617.42","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 60:10","first_present":1607,"ms":10032.248},"presents":80,"frame_ms":{"p50":120.053,"p90":153.035,"p99":176.428,"max":176.428},"gpu_busy_ms_per_present":78.503,"worker_busy_pct":{"queue worker 0x0":99.252,"queue worker 0x20":99.363},"top_phases_ms_per_frame":{"Driver.Packet":248.864,"Driver.Packet.dispatch":90.453,"Driver.Dispatch":89.677,"Driver.Packet.memory_wait":54.234,"Driver.Packet.device_lock":41.955,"Vulkan.Dispatch":40.617,"Vulkan.Dispatch.shader_resources":37.133,"Driver.Packet.draw":35.767},"trace_dropped":false,"trace":"aps5-trace-184c4f6ff78a-32848.json.gz"}

cl-main:

{"commit":"3a84d7c90ec6","dirty":false,"env":{"APS5_BATCH_WRITEBACK_WINDOWS":"0","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_TEXTURE_CACHE_MIB":"3072","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:20"},"gpu":"NVIDIA GeForce RTX 5070 Laptop GPU","driver":"NVIDIA 617.42","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 60:20","first_present":1653,"ms":20084.022},"presents":166,"frame_ms":{"p50":118.371,"p90":133.261,"p99":151.945,"max":154.821},"gpu_busy_ms_per_present":79.556,"worker_busy_pct":{"queue worker 0x0":99.263,"queue worker 0x20":99.341},"top_phases_ms_per_frame":{"Driver.Packet":240.130,"Driver.Packet.dispatch":76.829,"Driver.Dispatch":75.653,"Driver.Packet.memory_wait":57.848,"Driver.Packet.draw":42.803,"Driver.Draw":42.668,"Vulkan.Dispatch":41.119,"Driver.Packet.device_lock":40.975},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":12.057,"events_lost":174434,"totals_full":false}},"trace":"aps5-trace-3a84d7c90ec6-34472.json.gz"}

cl-both:

{"commit":"97a668ea2080","dirty":false,"env":{"APS5_BATCH_WRITEBACK_WINDOWS":"0","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_TEXTURE_CACHE_MIB":"3072","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"60:20"},"gpu":"NVIDIA GeForce RTX 5070 Laptop GPU","driver":"NVIDIA 617.42","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 60:20","first_present":1675,"ms":20017.651},"presents":180,"frame_ms":{"p50":109.582,"p90":119.944,"p99":130.642,"max":131.130},"gpu_busy_ms_per_present":78.416,"worker_busy_pct":{"queue worker 0x0":99.200,"queue worker 0x20":99.614},"top_phases_ms_per_frame":{"Driver.Packet":220.934,"Driver.Packet.dispatch":95.835,"Driver.Dispatch":95.194,"Driver.Packet.memory_wait":45.714,"Vulkan.Dispatch":41.171,"Vulkan.Dispatch.shader_resources":38.749,"Resources.Complete":37.321,"Driver.Packet.device_lock":30.603},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":13.164,"events_lost":138652,"totals_full":false}},"trace":"aps5-trace-97a668ea2080-32748.json.gz"}

Digests: python3 tools/trace_digest.py <trace> (the tool is in the squashed PR branches' base, tools/trace_digest.py); the outputs of the two 10 s captures are in lab/pending/done/44-cpu-pr-arms.log (section "clean rows").

AI-assisted: yes

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6208 docs/dev/ARCHITECTURE.md L101
PR 6804 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp L4208-4209
PR 6931 docs/dev/ARCHITECTURE.md L101

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1395 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4650 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4891 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5431 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5441 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ARCHITECTURE.md
PR 5998 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6012 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6044 docs/dev/ARCHITECTURE.md
PR 6146 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6222 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6243 docs/dev/ARCHITECTURE.md
PR 6259 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6340 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6412 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ARCHITECTURE.md
PR 6414 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6454 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ARCHITECTURE.md
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6492 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6647 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6783 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6788 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6887 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6946 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ARCHITECTURE.md
PR 6947 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ARCHITECTURE.md
PR 6965 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6973 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6987 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6989 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6995 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7002 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7201 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7258 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp

@github-actions github-actions Bot added docs agc libSceAgc* labels Oct 16, 2026
@parl0v

parl0v commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

A second machine for this PR and #6632: i5-14400F, RTX 4060 8 GB, 16 GB DDR4, Windows 10, Minecraft Dungeons II title screen. Libs main 5d9dcc4, + #6631 (a447c96), + #6632 (d98b21f), + both; 200 s runs with APS5_PROFILE_DRAW=1 APS5_TEXTURE_CACHE_MIB=3072, the four arms alternating, 3 rounds.

arm FPS r1 / r2 / r3 vs main, same round frame time GPU idle gaps
main 6.49 / 6.56 / 6.73 151.7 ms 51 ms
+ #6631 6.89 / 7.05 / 7.33 +7.5 % mean (+6.2 to +8.9) 141.0 ms 43 ms
+ #6632 6.77 / 6.83 / 6.80 +3.2 % mean (+1.0 to +4.3) 147.1 ms 47 ms
+ both 7.28 / 7.29 / 7.25 +10.3 % mean (+7.7 to +12.2) 137.6 ms 39 ms

GPU busy stays at 98-100 ms per frame in every arm, so the gain is the idle gap closing; the two levers add up (-10.7 and -4.6 ms alone, -14.2 ms together). 0 fatal and 0 wait timeouts in all 12 runs.

AI-assisted: yes (Claude Code)

@AlxcBTW

AlxcBTW commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

A second machine, measured by @parl0v: i5-14400F, RTX 4060 8 GB, 16 GB DDR4, Windows 10. Minecraft Dungeons II title screen, 200 s rows with APS5_PROFILE_DRAW=1 APS5_TEXTURE_CACHE_MIB=3072, four arms alternating over three rounds:

arm FPS, mean of 3 rounds vs main, same round GPU busy idle gaps
main 5d9dcc4 6.59 100 ms 51 ms
+ this PR (a447c96) 7.09 +7.5 % (+6.2 / +7.5 / +8.9) 98 ms 43 ms
+ #6632 (d98b21f) 6.80 +3.2 % (+4.3 / +4.1 / +1.0) 99 ms 47 ms
+ both 7.27 +10.3 % (+12.2 / +11.1 / +7.7) 98 ms 39 ms

The frame time falls from 151.7 to 137.6 ms with both. Nearly all of that is idle gap, while GPU busy moves by 2 ms. There were no fatal lines and no wait timeouts in the 12 runs. The full table and method are here: AlxcBTW#56 (comment)

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 16, 2026
… the GPU mutex

The stage-A pre-sync of a dispatch listed only the surfaces that stage B reads on the CPU. The DCC
keys of an image that a recorded kernel writes were not in the list, so stage B waited for that
kernel in WaitForKeyWriters with the GPU mutex held and every other worker queued behind the wait.
At the Minecraft Dungeons II title the pre-sync fired once in 296,000 dispatch checks while
WaitForKeyWriters synchronized 400 times per 10 s.

PresyncSurfaces now also returns the key range of every image binding (PresyncSurface::dccKeys).
presync waits for a key range only while recorded work writes it, the key-store memo does not
already give its value (DccKeyWritersPending) and, under the mutex it takes for a moment anyway, the
pending stores do not give the keys either (DccKeyWriterNeedsSync, the test WaitForKeyWriters makes
before it synchronizes). Which keys the dispatch reads, and when, is unchanged: the locked lookup runs
as before and finds nothing pending.

APS5_NO_DCC_PRESYNC=1 leaves the key ranges out. The [presync] line counts key ranges checked,
pre-syncs that only the keys required and the outcomes of WaitForKeyWriters. The test
agc_driver_dcc_key_presync covers a kernel that writes the keys, a recorded fill of the keys and
nothing pending, and skips with the reasons check_skips.py expects when the device has no timeline
semaphores or no host import.
@AlxcBTW
AlxcBTW force-pushed the perf/dcc-key-presync branch from a447c96 to 2a15be0 Compare October 16, 2026 12:52
@AlxcBTW

AlxcBTW commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main eb901bc, which has #1747, #4943, #4649, #5875, #5872 and #6784. Head is now 2a15be0. The only conflict was the queue-0 paragraph of docs/dev/ARCHITECTURE.md. I kept upstream's text there, including #1747's sentence on pipelined dispatches, and put this PR's paragraph next to it. The code merged without conflicts and is unchanged.

Fork CI, Linux with lavapipe, every agc_ test: 626 of 627 pass (run 38899800091). The one failure is agc_driver_graphics, which fails on main + #6784 without this PR as well (run 38898087865). The game numbers on this PR and on #6632 were measured on the earlier base and were not re-run on this one.

AI-assisted: yes

@oneandonlydean

Copy link
Copy Markdown
Collaborator

I went through this with #6632 and it reads as a safe optimisation: stage B still calls WaitForKeyWriters under the lock, so a stale presync only costs a wait. One question: DccKeyWriterNeedsSync doesn't apply the PendingFillsLeaveUncompressed short-circuit that WaitForKeyWriters has, so presync can wait for a fill the locked path wouldn't sync on. Is that intentional? On our side both ran clean on Astro Bot (4090, 4K, three levels), with no fps change to separate from noise there, which fits these levels being GPU-bound. Your and @parl0v's Minecraft Dungeons II rounds are the gain evidence. Next for us are the gate runs.

@oneandonlydean
oneandonlydean merged commit b4f05ab into boykopovar:main Oct 17, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants